package com.imooc.filter;

import com.google.gson.Gson;
import com.imooc.base.BaseInfoProperties;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.JWTUtils;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @version 1.0
 * @Author Mr.Yang
 * @Date 2024-08-07 16:39
 * @question:
 */

@Component
@Slf4j
public class SecurityFilterJWT extends BaseInfoProperties implements GlobalFilter, Ordered {

    public static final String HEADER_USER_TOKEN = "headerUserToken";

    @Autowired
    private ExcludeUrlProperties excludeUrlProperties;

    @Autowired
    private JWTUtils jwtUtils;

    //路径匹配的规则器
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //获取当前的请求路径
        String url = exchange.getRequest().getURI().getPath();
        log.info("SecurityFilterJWT url=" + url);
        //获取所有不需要校验的路径
        List<String> excludeList = excludeUrlProperties.getUrls();
        //当使用springBoot本地上传图片的时候 图片资源也不应该被拦截
        String fileStart = excludeUrlProperties.getFileStart();

        //不需要排除的路径不为null
        if (excludeList != null && !excludeList.isEmpty()) {
            for (String excludeUrl : excludeList) {
                //使用规则匹配器去匹配
                if (antPathMatcher.match(excludeUrl, url)) {
                    //匹配到 直接放行
                    return chain.filter(exchange);
                }
            }
        }
        if (StringUtils.isNotBlank(fileStart)) {
            boolean matchStartFiles = antPathMatcher.matchStart(fileStart, url);
            if (matchStartFiles) {
                // 如果匹配到，则直接放行
                return chain.filter(exchange);
            }
        }

        //此处也可根据指定的url,对ip来进行判断拦截,限制访问次数

        //能到达此处证明被拦截
        log.info("被拦截了......");

        //从请求头去获得jwt 判断是否有问题
        String userToken = exchange.getRequest().getHeaders().getFirst(HEADER_USER_TOKEN); //app@token
        if(StringUtils.isNotBlank(userToken)) {
            String[] tokenArr = userToken.split(JWTUtils.at);
            if (tokenArr.length < 2) {
                //长度小于2 说明token有问题
                return renderErrorMsg(exchange, ResponseStatusEnum.JWT_LENGTH_ERROR);
            }
            //获取前缀与jwt令牌
            String prefix = tokenArr[0];
            String jwt = tokenArr[1];

            //判断并且处理用户信息
            if (prefix.equalsIgnoreCase(TOKEN_USER_PREFIX)) {
                return dealJWT(jwt, exchange, chain, APP_USER_JSON);
            } else if (prefix.equalsIgnoreCase(TOKEN_SAAS_PREFIX)) {
                return dealJWT(jwt, exchange, chain, SAAS_USER_JSON);
            } else if (prefix.equalsIgnoreCase(TOKEN_ADMIN_PREFIX)) {
                return dealJWT(jwt, exchange, chain, ADMIN_USER_JSON);
            }

//            return dealJWT(jwt, exchange, chain, APP_USER_JSON);
        }

        //到最后则代表后续的路径都不满足条件 则不进行放行
        //因为 自己自定义的拦截器无法拦截网关当中的过滤方法 所以继续自定义返回错误
//        GraceException.display(ResponseStatusEnum.UN_LOGIN);
//        return chain.filter(exchange);
        return renderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);
    }

    /**
     * 判断并且处理用户信息
     *      需要try-catch起来 因为可能会抛出异常 比如token时间过期等
     * @param jwt 已经被处理好的jwt 第二部分
     * @param exchange
     * @param chain
     * @param key
     * @return
     */
    private Mono<Void> dealJWT(String jwt, ServerWebExchange exchange, GatewayFilterChain chain, String key) {

        try {
            String userJson = jwtUtils.checkJWT(jwt);

            //获取用户信息之后 可以选择携带该用户信息进行一个向后传递
            //选择构建新的request并将用户信息放入请求头当中
            ServerWebExchange serverWebExchange = setNewHeader(exchange,key,userJson);

            return chain.filter(serverWebExchange);
        } catch (ExpiredJwtException e) {
            //token过期
            e.printStackTrace();
            return renderErrorMsg(exchange, ResponseStatusEnum.JWT_EXPIRE_ERROR);
        } catch (Exception e) {
            e.printStackTrace();
            return renderErrorMsg(exchange, ResponseStatusEnum.JWT_SIGNATURE_ERROR);
        }

    }

    /**
     * 设置新的请求头信息 进行向后传递 使接口可以直接通过request来获取用户信息
     * @param exchange
     * @param headerKey
     * @param headerValue
     * @return
     */
    private ServerWebExchange setNewHeader(ServerWebExchange exchange, String headerKey, String headerValue) {

        //构建新的request
        ServerHttpRequest newRequest = exchange.getRequest().mutate().header(headerKey, headerValue).build();
        //使新的request替换掉原来的exchange
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

        return newExchange;
    }

    /**
     * 重新包装并且返回错误信息
     * @param exchange
     * @param statusEnum
     * @return
     */
    private Mono<Void> renderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum statusEnum) {

        //获得response
        ServerHttpResponse response = exchange.getResponse();
        //构建jsonResult
        GraceJSONResult jsonResult = GraceJSONResult.exception(statusEnum);
        //修改response的code码为500(客户端返回的不一定是500 是返回的自己的状态码)
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        //设定header类型
        if (response.getHeaders().containsKey("Content-Type"))
            response.getHeaders().add("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);

        //转换json并向response中写入数据
        String resultJson = new Gson().toJson(jsonResult);
        DataBuffer dataBuffer = response.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8));

        return response.writeWith(Mono.just(dataBuffer));
    }

    //过滤器的顺序 数字越小优先级则越大
    @Override
    public int getOrder() {
        return 0;
    }


}
